package gepetto.message;

import java.io.IOException;
import java.io.StringReader;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.TreeMap;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

public class XMLMessageCodec implements MessageCodec {
	private static final int BUFFER_SIZE = 16 * 1024;

	private static final String MESSAGE_TAG = "message";
	private static final String MESSAGE_START = "<" + MESSAGE_TAG + " ";
	private static final String MESSAGE_FINAL = "</" + MESSAGE_TAG + ">";

	private static final String MODULE_TAG = "module";
	private static final String SEQUENCE_TAG = "sequence";
	private static final String ID_TAG = "id";
	private static final String SET_TAG = "set";
	private static final String ATTR_TAG = "attr";
	private static final String VALUE_TAG = "value";

	private static DocumentBuilder builder;

	static {
		try {
			XMLMessageCodec.builder = DocumentBuilderFactory.newInstance()
					.newDocumentBuilder();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private StringBuffer in;
	private StringBuffer out;
	private LinkedList<Message> encoded;

	public XMLMessageCodec() {
		in = new StringBuffer();
		out = new StringBuffer();
		encoded = new LinkedList<Message>();
	}

	public int add(byte[] data) {
		in.append(new String(data));

		while (true) {
			if (in.length() == 0) {
				break;
			}

			int start = in.indexOf(MESSAGE_START);
			if (start < 0) {
				start = in.indexOf("<");
				if (start < 0) {
					in.setLength(0);
				} else {
					in.delete(0, start);
				}
				break;
			}
			if (start > 0) {
				in.delete(0, start);
			}

			int end = in.indexOf(MESSAGE_FINAL);
			if (end < 0) {
				if (in.length() > BUFFER_SIZE) {
					return ERROR;
				} else {
					break;
				}
			}
			end += MESSAGE_FINAL.length();
			String message = in.substring(start, end);
			in.delete(start, end);
			LinkedList<Message> msgs = this.decode(message);
			encoded.addAll(msgs);
		}

		return (encoded.size() > 0) ? HAS_MESSAGES : NO_MESSAGES;
	}

	private LinkedList<Message> decode(String message) {
		LinkedList<Message> messages = new LinkedList<Message>();
		Document doc;
		try {
			doc = builder.parse(new InputSource(new StringReader(message)));
		} catch (SAXException e1) {
			e1.printStackTrace();
			return messages;
		} catch (IOException e1) {
			e1.printStackTrace();
			return messages;
		}
		NodeList list = doc.getElementsByTagName(MESSAGE_TAG);

		for (int i = 0; i < list.getLength(); ++i) {
			Node nmsg = list.item(i);
			NamedNodeMap map = nmsg.getAttributes();
			Node tmp;
			tmp = map.getNamedItem(MODULE_TAG);
			if (tmp == null) {
				continue;
			}
			String module = tmp.getNodeValue();
			tmp = map.getNamedItem(ID_TAG);
			if (tmp == null) {
				continue;
			}
			String id = tmp.getNodeValue();
			tmp = map.getNamedItem(SEQUENCE_TAG);
			if (tmp == null) {
				continue;
			}
			String sseq = tmp.getNodeValue();
			int seq;
			try {
				seq = Integer.parseInt(sseq);
			} catch (NumberFormatException e) {
				continue;
			}
			
			Message msg = new Message (module, id, seq);
			this.addAttributes(msg, nmsg, "");
			
			messages.add(msg);
		}

		return messages;
	}
	
	private void addAttributes (AttrSet set, Node node, String baseKey) {
		NodeList children = node.getChildNodes();
		for (int j = 0; j < children.getLength(); ++j) {
			Node child = children.item(j);
			if (child.getNodeName().equals (SET_TAG)) {
				NamedNodeMap map = child.getAttributes();
				Node key = map.getNamedItem (ID_TAG);
				if (key != null) {
					String setKey = key.getNodeValue();
					if (setKey != null) {
						addAttributes(set, child, baseKey + setKey + ".");
					}
				}
			}
			else if (child.getNodeName().equals(ATTR_TAG)) {
				NamedNodeMap map = child.getAttributes();
				Node key = map.getNamedItem(ID_TAG);
				Node value = map.getNamedItem(VALUE_TAG);
				if ((key != null) && (value != null)) {
					String attrKey = key.getNodeValue();
					String attrValue = value.getNodeValue();
					if ((attrKey != null) && (attrValue != null)) {
						set.put(baseKey + attrKey, attrValue);
					}
				}
			}
		}
	}

	public void add(Message msg) {
		out.append(MESSAGE_START +
				   MODULE_TAG   + "=\"" + msg.getModule()   + "\" " +
				   ID_TAG       + "=\"" + msg.getId()       + "\" " +
				   SEQUENCE_TAG + "=\"" + msg.getSequence() + "\">");
		getAttributes (out, msg);
		out.append (MESSAGE_FINAL);
	}

	private void getAttributes(StringBuffer out, AttrSet set) {
		TreeMap<String, String> attrs = set.getAttrs();
		for (Iterator<String> it = attrs.keySet().iterator(); it.hasNext(); ) {
			String id = it.next();
			out.append("<" + ATTR_TAG + " " + ID_TAG + "=\"" + id + "\" " + VALUE_TAG + "=\"" + attrs.get (id) + "\"" + " />");
		}
		
		TreeMap<String, AttrSet> sets = set.getAttrSets();
		for (Iterator<String> it = sets.keySet().iterator(); it.hasNext(); ) {
			String id = it.next();
			out.append("<" + SET_TAG + " " + ID_TAG + "=\"" + id + "\">");
			getAttributes (out, sets.get(id));
			out.append("</" + SET_TAG + ">");
		}
	}

	public List<Message> get() {
		LinkedList<Message> rt = (LinkedList<Message>) encoded.clone();
		encoded.clear();
		return rt;
	}

	public int get (byte[] buf) {
		if (buf.length == 0) {
			return 0;
		}
		
		if (out.length() == 0) {
			return -1;
		}
		
		int len = (buf.length >> 1) << 1;
		len = (len > out.length()) ? out.length() : len;
		
		String rt = out.substring (0, len);
		out.delete(0, len);
		String tmp = new String (rt.getBytes());
		System.arraycopy (rt.getBytes(), 0, buf, 0, len);
		
		return len;
	}
}
